/**
 * 
 */
package br.com.dp.servidor.conversor.generico;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.Set;

import org.reflections.Reflections;

import br.com.dp.dto.DTO;
import br.com.dp.dto.dominio.ConversorEnum;
import br.com.dp.dto.excecao.BusinessException;
import br.com.dp.dto.excecao.ErrorCode;
import br.com.dp.servidor.conversor.anotacao.Converte;


/**
 * @author emmanuel
 * Classe responsável por chamar um conversor específico.
 */
public class Conversor {
	final static String NOME_METODO = "converteDTO";
	final static String PACOTE_CONVERSORES = "br.com.dp.servidor.conversor.conversores";
	
	/**
	 * Método responsável por invocar o método de um determinado conversor, identificado por um {@link ConversorEnum}
	 * @param conversorEnum Conversor específico.
	 * @param objetoDTO DTO de envio ao conversor.
	 * @param mapa Mapa de objetos, caso haja a necessidade de passar outros parâmetros.
	 * @return DTO específico.
	 * @throws BusinessException
	 */
	@SuppressWarnings("rawtypes")
	public static DTO converteDTO(ConversorEnum conversorEnum, DTO objetoDTO, Map<String, Object> mapa) throws BusinessException{
		// A linha abaixo pega todas as informações de um determinado pacote.
		Reflections reflections = new Reflections(PACOTE_CONVERSORES);
		// A linha abaixo pega todas as classes que implementam a interface: ConversorInterface
		Set<Class<? extends ConversorInterface>> subTipos = reflections.getSubTypesOf(ConversorInterface.class);
		for (Class<?> classe : subTipos) {
			// A linha abaixo verifica se a classe contêm o a anotação: Converte.class
			if(classe.isAnnotationPresent(Converte.class)){
				// A linha abaixo pega as informações contidas na anotação da classe.
				Converte converte = classe.getAnnotation(Converte.class);
				// A linha abaixo verifica se a anotação da classe é igual à passada por parâmetro. 
				if(converte.conversor() == conversorEnum){
					try {
						// A linha abaixo pega um método específico da classe, através do nome do método, e o tipo da classe de seus parâmetros.
						Method metodo = classe.getDeclaredMethod(NOME_METODO, new Class[]{objetoDTO.getClass(), Map.class});
						// A linha abaixo invoca o método, passando uma nova instância da classe, e os parâmetros do método da classe.
						DTO dto = (DTO) metodo.invoke(classe.newInstance(), objetoDTO, mapa);
						return dto;
					}
					catch (NoSuchMethodException | SecurityException | IllegalAccessException |
							IllegalArgumentException | InvocationTargetException | InstantiationException e) {
						throw new BusinessException(ErrorCode.ERRO_CONVERSAO);
					}
				}
			}
		}
		return null;
	}
}
